package com.shareppy.utils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class ReflectionUtils {

	private ReflectionUtils() {
		super();
	}

	/**
	 * Valida si un campo es entero de java
	 * 
	 * @param field
	 *            El campo a validar
	 * @return true si y solo si el campo es de tipo int o Integer
	 */
	public static boolean isInt(Field field) {
		return field.getType() == Integer.class || field.getType() == int.class;
	}

	/**
	 * Valida si un campo es long de java
	 * 
	 * @param field
	 * @return true si y solo si el campo es de tipo long o Long
	 */
	public static boolean isLong(Field field) {
		return field.getType() == Long.class || field.getType() == long.class;
	}

	/**
	 * Valida si un campo es de tipo float de java
	 * 
	 * @param field
	 * @return true si y solo si el campo es de tipo float o Float
	 */
	public static boolean isFloat(Field field) {
		return field.getType() == Float.class || field.getType() == float.class;
	}

	/**
	 * Valida si un campo es de tipo double de java
	 * 
	 * @param field
	 * @return true si y solo si el campo es de tipo double o Double
	 */
	public static boolean isDouble(Field field) {
		return field.getType() == Double.class
				|| field.getType() == double.class;
	}

	/**
	 * Valida si un campo es de tipo INTEGER de SQLite
	 * 
	 * @param field
	 * @return true si el campo es de tipo int, Integer, long o Long
	 */
	public static boolean isInteger(Field field) {
		return isInt(field) || isLong(field);
	}

	/**
	 * Valida si un campo es de tipo REAL de SQLite
	 * 
	 * @param field
	 * @return true si el campo es de tipo double, Double, float o Float
	 */
	public static boolean isReal(Field field) {
		return isFloat(field) || isDouble(field);
	}

	/**
	 * Valida si un campo es de tipo TEXT de SQLite
	 * 
	 * @param field
	 * @return true si el campo es de tipo String
	 */
	public static boolean isText(Field field) {
		return field.getType() == String.class;
	}

	/**
	 * Obtiene un array con los campos que est��n en una entidad, si la entidad
	 * est�� anotada con @Inherited, se retornan los campos de la superclase
	 * 
	 * @param entity
	 * @return
	 */
	public static Field[] getFields(Object entity,
			Class<? extends Annotation> superClassAnnotation) {
		return getFields(entity.getClass(), superClassAnnotation);
	}

	public static Field[] getFields(Class<?> clazz,
			Class<? extends Annotation> superClassAnnotation) {
		if (ReflectionUtils.hasAnnotation(clazz, superClassAnnotation)) {
			return clazz.getSuperclass().getDeclaredFields();
		}
		return clazz.getDeclaredFields();
	}

	/**
	 * Ontiene un campo de una entidad
	 * 
	 * @param entity
	 * @param field
	 * @return
	 * @throws NoSuchFieldException
	 */
	public static Field getField(Object entity, String field,
			Class<? extends Annotation> superClassAnnotation)
			throws NoSuchFieldException {
		return ReflectionUtils.getField(entity.getClass(), field,
				superClassAnnotation);
	}

	/**
	 * Obtiene un campo de una entidad de una clase.
	 * 
	 * @param clazz
	 * @param field
	 * @return
	 * @throws NoSuchFieldException
	 */
	public static Field getField(Class<?> clazz, String field,
			Class<? extends Annotation> superClassAnnotation)
			throws NoSuchFieldException {
		if (ReflectionUtils.hasAnnotation(clazz, superClassAnnotation)) {
			return clazz.getSuperclass().getDeclaredField(field);
		}
		return clazz.getDeclaredField(field);
	}

	/**
	 * Obtiene un campo de una entidad de una clase.
	 * 
	 * @param clazz
	 * @param field
	 * @return
	 * @throws NoSuchFieldException
	 */
	public static Field getAccessibleField(Class<?> clazz, String field,
			Class<? extends Annotation> superClassAnnotation)
			throws NoSuchFieldException {
		Field f = null;
		if (ReflectionUtils.hasAnnotation(clazz, superClassAnnotation)) {
			f = clazz.getSuperclass().getDeclaredField(field);
		} else {
			f = clazz.getDeclaredField(field);
		}
		f.setAccessible(true);
		return f;
	}

	public static Object getFieldValue(Object entity, Field field)
			throws Exception {
		field.setAccessible(true);
		if (ReflectionUtils.isInt(field)) {
			return field.getInt(entity);
		} else if (ReflectionUtils.isLong(field)) {
			return field.getLong(entity);
		} else if (ReflectionUtils.isFloat(field)) {
			return field.getFloat(entity);
		} else if (ReflectionUtils.isDouble(field)) {
			return field.getDouble(entity);
		} else if (ReflectionUtils.isText(field)) {
			return (String) field.get(entity);
		} else {
			throw new Exception(field.getType().getName()
					.concat(" is not supported."));
		}
	}

	/**
	 * Valida que una entidad tenga la anotaci��n indicada
	 * 
	 * @param instance
	 * @param annotationclass
	 * @throws Exception
	 */
	public static <T, A extends Annotation> void checkAnnotation(T instance,
			Class<A> annotationclass) throws Exception {
		if (!ReflectionUtils.hasAnnotation(instance, annotationclass)) {
			throw new Exception(instance
					.getClass()
					.getName()
					.concat(" haven't ".concat(annotationclass.getName()
							.concat(" anotation."))));
		}
	}

	protected static <T, A extends Annotation> boolean hasAnnotation(
			T instance, Class<A> annotationclass) {
		return ReflectionUtils.hasAnnotation(instance.getClass(),
				annotationclass);
	}

	protected static <A extends Annotation> boolean hasAnnotation(
			Class<?> entityClass, Class<A> annotationclass) {
		Object annotation = entityClass.getAnnotation(annotationclass);
		return annotation != null;
	}

	@SuppressWarnings("unchecked")
	public static <T> T newInstance(T entity) throws InstantiationException,
			IllegalAccessException {
		return (T) newInstance(entity.getClass());
	}

	public static <T> T newInstance(Class<T> entityClass)
			throws InstantiationException, IllegalAccessException {
		return (T) entityClass.newInstance();
	}

	/**
	 * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
	 *
	 * @param packageName The base package
	 * @return The classes
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public static Class[] getClasses(String packageName)
	        throws ClassNotFoundException, IOException {
	    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	    assert classLoader != null;
	    String path = packageName.replace('.', '/');
	    Enumeration<URL> resources = classLoader.getResources(path);
	    List<File> dirs = new ArrayList<File>();
	    while (resources.hasMoreElements()) {
	        URL resource = resources.nextElement();
	        dirs.add(new File(resource.getFile()));
	    }
	    ArrayList<Class> classes = new ArrayList<Class>();
	    for (File directory : dirs) {
	        classes.addAll(findClasses(directory, packageName));
	    }
	    return classes.toArray(new Class[classes.size()]);
	}

	/**
	 * Recursive method used to find all classes in a given directory and subdirs.
	 *
	 * @param directory   The base directory
	 * @param packageName The package name for classes found inside the base directory
	 * @return The classes
	 * @throws ClassNotFoundException
	 */
	public static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
	    List<Class> classes = new ArrayList<Class>();
	    if (!directory.exists()) {
	        return classes;
	    }
	    File[] files = directory.listFiles();
	    for (File file : files) {
	        if (file.isDirectory()) {
	            assert !file.getName().contains(".");
	            classes.addAll(findClasses(file, packageName + "." + file.getName()));
	        } else if (file.getName().endsWith(".class")) {
	            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
	        }
	    }
	    return classes;
	}
	
}
